package top.suven.base.http.processor.url;

import com.google.common.collect.Lists;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import top.suven.base.http.processor.url.annotations.AnnotationListener;
import top.suven.base.http.processor.url.annotations.Observer;

import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 标签实现解释器实现类;
 * 作用,实现此接口的类会收到自己所关心的注解实例注入
 * 方法必须带 @Observer注解表明可以在这个方法上注入
 * 方法只能有两个参数, 实现自己想得到的注解
 *
 */
public class AnnotationInterpreter {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private Reflections reflections;

    private static Set<Class> set = new HashSet<>();

    /**收集所有标签;**/
    public static Set<Class> getAnnoTypeSet(){
        return new HashSet<Class>(set);
    }

    /**
     * 初始化搜索实现AnnotationListener的所有标签类;
     * 并实现标签类,并执行对应标签类的实现方法;
     */
    @PostConstruct
    public void init() {
        //获取扩写标签的所有实现类的类聚合;
        Set<Class<? extends AnnotationListener>> module = reflections.getSubTypesOf(AnnotationListener.class);

        for (Class claxx : module) {
            //获取该类上指定Observer标签的方法
            List<Method> methods = findObserverMethod(claxx);
            for (Method method : methods) {
                Class<?>[] types = method.getParameterTypes();
                if (null == types ) { continue; }

                Class annoType = types[0];
                set.add(annoType);
                //获取添加该标识所有类的属性和对应的标识;
                Map<String, Object> urlToAnno = findFieldAnnotationByAnnoType(annoType);
                for (Map.Entry<String, Object> entry : urlToAnno.entrySet()) {
                    this.invoke(method,claxx,entry);
                }
            }
        }


    }

    /**
     * 将标签数据映射到指到的实现对象的方法中;
     * @param method
     * @return
     */
    private Object invoke(Method method, Class claxx, Map.Entry<String, Object>entry) {
        try {
            Object target = claxx.newInstance();
            String topic = entry.getKey();
            Object annoValue = entry.getValue();
            method.setAccessible(true);
            return method.invoke(target,annoValue,topic);
        } catch (Exception e) {
            logger.warn("", e);
        }
        return null;
    }



    /**
     * 根据给定的注解查找类中每个字段的值
     * @param annoType 给定的注解
     * @return 一个map, key是字段值, value是该字段上面注解的值
     */
    private Map<String, Object> findFieldAnnotationByAnnoType(Class<? extends Annotation> annoType) {
        Map<String, Object> urlToAnno = new HashMap<>();
        Set<Class<?>> clazzSet = reflections.getTypesAnnotatedWith(annoType);
        if (clazzSet.isEmpty()) {
            logger.error("注解:{}必须存在于类上", annoType);
        }
        for (Class<?> klass : clazzSet) {
            Annotation head = AnnotationUtils.findAnnotation(klass, annoType);
//            Method[] Methods = klass.getMethods();
            Field[] fields = FieldUtils.getAllFields(klass);// klass.getFields();
            for (Field field : fields) {
                Annotation anno = AnnotationUtils.getAnnotation(field, annoType);
                if (anno == null) {
                    continue;
                }
                try {
                    String fieldName = null;
                    if(klass.isInterface()){
                        fieldName = FieldUtils.readStaticField(field).toString();
                    }else {
                        fieldName = klass.getName()+"," + field.getName();
                    }
                    urlToAnno.put(fieldName, anno);
                } catch (Exception e) {
                    logger.warn("", e);
                }
            }
        }
        return urlToAnno;
    }


    /**
     * 搜索含有标签Observer的,此类的实现类的方法的集合;
     * @param klass
     * @return
     */
    private List<Method> findObserverMethod(Class<?> klass) {
        Method[] methods = klass.getDeclaredMethods();
        List<Method> methodList = Lists.newArrayList();
        for (Method method : methods) {
            top.suven.base.http.processor.url.annotations.Observer observer = AnnotationUtils.findAnnotation(method, Observer.class);
            if (observer != null) {
                methodList.add(method);
            }
        }
        return methodList;
    }

}